/* -*- Mode: C; tab-width: 4 -*-
 *
 * Copyright (c) 2002-2003 Apple Computer, Inc. All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <stdio.h>                      // For printf()
#include <string.h>                     // For strlen() etc.

#include <Events.h>                     // For WaitNextEvent()
#include <SIOUX.h>                      // For SIOUXHandleOneEvent()

#include "mDNSEmbeddedAPI.h"            // Defines the interface to the client layer above

#include "mDNSMacOS9.h"                 // Defines the specific types needed to run mDNS on this platform

// These don't have to be globals, but their memory does need to remain valid for as
// long as the search is going on. They are declared as globals here for simplicity.
static mDNS m;
static mDNS_PlatformSupport p;
static ServiceRecordSet p1, p2;
static AuthRecord availRec1, availRec2;
static Boolean availRec2Active;

// This sample code just calls mDNS_RenameAndReregisterService to automatically pick a new
// unique name for the service. For a device such as a printer, this may be appropriate.
// For a device with a user interface, and a screen, and a keyboard, the appropriate
// response may be to prompt the user and ask them to choose a new name for the service.
mDNSlocal void Callback(mDNS *const m, ServiceRecordSet *const sr, mStatus result)
{
    switch (result)
    {
    case mStatus_NoError:      debugf("Callback: %##s Name Registered",   sr->RR_SRV.resrec.name.c); break;
    case mStatus_NameConflict: debugf("Callback: %##s Name Conflict",     sr->RR_SRV.resrec.name.c); break;
    case mStatus_MemFree:      debugf("Callback: %##s Memory Free",       sr->RR_SRV.resrec.name.c); break;
    default:                   debugf("Callback: %##s Unknown Result %d", sr->RR_SRV.resrec.name.c, result); break;
    }

    if (result == mStatus_NameConflict) mDNS_RenameAndReregisterService(m, sr, mDNSNULL);
}

// RegisterService() is a simple wrapper function which takes C string
// parameters, converts them to domainname parameters, and calls mDNS_RegisterService()
mDNSlocal void RegisterService(mDNS *m, ServiceRecordSet *recordset,
                               UInt16 PortAsNumber, const char txtinfo[],
                               const domainlabel *const n, const char type[], const char domain[])
{
    domainname t;
    domainname d;
    char buffer[MAX_ESCAPED_DOMAIN_NAME];
    UInt8 txtbuffer[512];

    MakeDomainNameFromDNSNameString(&t, type);
    MakeDomainNameFromDNSNameString(&d, domain);

    if (txtinfo)
    {
        strncpy((char*)txtbuffer+1, txtinfo, sizeof(txtbuffer)-1);
        txtbuffer[0] = (UInt8)strlen(txtinfo);
    }
    else
        txtbuffer[0] = 0;

    mDNS_RegisterService(m, recordset,
                         n, &t, &d,                 // Name, type, domain
                         mDNSNULL, mDNSOpaque16fromIntVal(PortAsNumber),
                         txtbuffer, (mDNSu16)(1+txtbuffer[0]), // TXT data, length
                         mDNSNULL, 0,               // Subtypes (none)
                         mDNSInterface_Any,         // Interface ID
                         Callback, mDNSNULL, 0);    // Callback, context, flags

    ConvertDomainNameToCString(recordset->RR_SRV.resrec.name, buffer);
    printf("Made Service Records for %s\n", buffer);
}

// RegisterFakeServiceForTesting() simulates the effect of services being registered on
// dynamically-allocated port numbers. No real service exists on that port -- this is just for testing.
mDNSlocal void RegisterFakeServiceForTesting(mDNS *m, ServiceRecordSet *recordset, const char txtinfo[],
                                             const char name[], const char type[], const char domain[])
{
    static UInt16 NextPort = 0xF000;
    domainlabel n;
    MakeDomainLabelFromLiteralString(&n, name);
    RegisterService(m, recordset, NextPort++, txtinfo, &n, type, domain);
}

// Done once on startup, and then again every time our address changes
mDNSlocal OSStatus mDNSResponderTestSetup(mDNS *m)
{
    char buffer[MAX_ESCAPED_DOMAIN_NAME];
    mDNSv4Addr ip = m->HostInterfaces->ip.ip.v4;

    ConvertDomainNameToCString(&m->MulticastHostname, buffer);
    printf("Name %s\n", buffer);
    printf("IP   %d.%d.%d.%d\n", ip.b[0], ip.b[1], ip.b[2], ip.b[3]);

    printf("\n");
    printf("Registering Service Records\n");
    // Create example printer discovery records
    //static ServiceRecordSet p1, p2;

    RegisterFakeServiceForTesting(m, &p1, "", "One", "_raop._tcp.", "local.");
    RegisterFakeServiceForTesting(m, &p2, "", "Two", "_raop._tcp.", "local.");

    return(kOTNoError);
}

mDNSlocal void AvailCallback(mDNS *const m, AuthRecord *const rr, mStatus result)
{
    Boolean *b = (Boolean *)rr->RecordContext;
    (void)m; // Unused
    // Signal that our record is now free for re-use
    if (result == mStatus_MemFree) *b = false;
}

mDNSlocal OSStatus mDNSResponderSetAvail(mDNS *m, AuthRecord *rr, ServiceRecordSet *sr)
{
    // 1. Initialize required fields of AuthRecord
    // 2. Set name of subtype PTR record to our special subtype name denoting "available" instances
    // 3. Set target of subtype PTR record to point to our SRV record (exactly the same as the main service PTR record)
    // 4. And register it
    mDNS_SetupResourceRecord(rr, mDNSNULL, mDNSInterface_Any, kDNSType_PTR, 2*3600, kDNSRecordTypeShared, AvailCallback, &availRec2Active);
    MakeDomainNameFromDNSNameString(rr->resrec.name, "a._sub._raop._tcp.local.");
    AssignDomainName(&rr->resrec.rdata->u.name, sr->RR_SRV.resrec.name);
    return(mDNS_Register(m, rr));
}

// YieldSomeTime() just cooperatively yields some time to other processes running on classic Mac OS
mDNSlocal Boolean YieldSomeTime(UInt32 milliseconds)
{
    extern Boolean SIOUXQuitting;
    EventRecord e;
    WaitNextEvent(everyEvent, &e, milliseconds / 17, NULL);
    SIOUXHandleOneEvent(&e);
    return(SIOUXQuitting);
}

int main()
{
    mStatus err;
    Boolean DoneSetup = false;
    mDNSs32 nextAvail, nextBusy;

    SIOUXSettings.asktosaveonclose = false;
    SIOUXSettings.userwindowtitle = "\pMulticast DNS Responder";

    printf("Multicast DNS Responder\n\n");
    printf("This software reports errors using MacsBug breaks,\n");
    printf("so if you don't have MacsBug installed your Mac may crash.\n\n");
    printf("******************************************************************************\n");

    err = InitOpenTransport();
    if (err) { debugf("InitOpenTransport failed %d", err); return(err); }

    err = mDNS_Init(&m, &p, mDNS_Init_NoCache, mDNS_Init_ZeroCacheSize,
                    mDNS_Init_AdvertiseLocalAddresses, mDNS_Init_NoInitCallback, mDNS_Init_NoInitCallbackContext);
    if (err) return(err);

    while (!YieldSomeTime(35))
    {
#if MDNS_ONLYSYSTEMTASK
        // For debugging, use "#define MDNS_ONLYSYSTEMTASK 1" and call mDNSPlatformIdle() periodically.
        // For shipping code, don't define MDNS_ONLYSYSTEMTASK, and you don't need to call mDNSPlatformIdle()
        extern void mDNSPlatformIdle(mDNS *const m);
        mDNSPlatformIdle(&m);   // Only needed for debugging version
#endif
        if (m.mDNSPlatformStatus == mStatus_NoError && !DoneSetup)
        {
            DoneSetup = true;
            printf("\nListening for mDNS queries...\n");
            mDNSResponderTestSetup(&m);
            mDNSResponderSetAvail(&m, &availRec1, &p1);
            availRec2Active = false;
            nextAvail = mDNS_TimeNow(&m) + mDNSPlatformOneSecond * 10;
            nextBusy  = mDNS_TimeNow(&m) + mDNSPlatformOneSecond * 15;
        }

        if (DoneSetup)
        {
            // We check availRec2.RecordType because we don't want to re-register this record
            // if the previous mDNS_Deregister() has not yet completed
            if (mDNS_TimeNow(&m) - nextAvail > 0 && !availRec2Active)
            {
                printf("Setting Two now available\n");
                availRec2Active = true;
                mDNSResponderSetAvail(&m, &availRec2, &p2);
                nextAvail = nextBusy + mDNSPlatformOneSecond * 10;
            }
            else if (mDNS_TimeNow(&m) - nextBusy > 0)
            {
                printf("Setting Two now busy\n");
                mDNS_Deregister(&m, &availRec2);
                nextBusy = nextAvail + mDNSPlatformOneSecond * 5;
            }
        }
    }

    if (p1.RR_SRV.resrec.RecordType) mDNS_DeregisterService(&m, &p1);
    if (p2.RR_SRV.resrec.RecordType) mDNS_DeregisterService(&m, &p2);
    if (availRec1.resrec.RecordType) mDNS_Deregister(&m, &availRec1);
    if (availRec2Active) mDNS_Deregister(&m, &availRec2);

    mDNS_Close(&m);

    return(0);
}
